#ifndef __SOCKET_HPP__
#define __SOCKET_HPP__

/* Socket模块：对套接字操作的 封装管理的模块。
    1. 创建套接字
    2. 设置套接字为非阻塞式IO
    3. 设置地址重用
    4. 为套接字绑定地址端口等信息
    5. 设置套接字为监听模式
    6. 获取新连接
    7. 发起新连接
    8. 关闭套接字
    9. 创建服务端监听套接字
    10. 创建客户端通信套接字
 */

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

/**
 * @brief 安全的创建套接字 功能。1：打开；0：关闭。
 * 此功能用于如下情况：使用一个初始化完毕的，且并未被关闭的套接字，假定为sockfd0，来初始化一个新的Socket，假定为sock1。
 * 如果该功能关闭，那么此种情况下，就会再创建一个新的套接字给sock1，而sockfd0就会变成“野生的”套接字
 */
#define SAFE_SOCKET 0

static const char* default_ip = "0.0.0.0";
static const int default_backlog = 1024;

class Socket
{
private:
    int _sockfd;
public:
    Socket(int sockfd = -1): _sockfd(sockfd)
    {}
    ~Socket()
    {
        Close();
    }
    /**
     * @brief 获取封装的文件描述符
     * 
     * @return int sockfd
     */
    int Fd() const
    {
        return _sockfd;
    }
private:
    /**
     * @brief 创建套接字
     * 
     * @return true 成功\
     * @return false 失败
     */
    bool CreateSocket()
    {
#if SAFE_SOCKET
        if(_sockfd > -1) return true;
#endif
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(_sockfd < 0)
        {
            ERR_LOG("SOCKET CREATE FAILED");
            return false;
        }

        // INF_LOG("SOCKET %d CREATE SUCCESS", _sockfd);

        return true;
    }
    /**
     * @brief 为套接字绑定ip端口等信息
     * 
     * @param ip 绑定的ip信息
     * @param port 绑定的port信息
     * @return true 绑定成功\
     * @return false 绑定失败
     */
    bool Bind(const std::string& ip, uint16_t port)
    {
        struct sockaddr_in addr;
        bzero(&addr, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(ip.c_str());
        addr.sin_port = htons(port);
        
        int ret = bind(_sockfd, (struct sockaddr*)&addr, (socklen_t)sizeof(addr));
        if(ret < 0)
        {
            ERR_LOG("SOCKET %d BIND FAILED: %d : %s", _sockfd, errno, strerror(errno));
            return false;
        }
        return true;
    }
    /**
     * @brief 开始监听新连接。服务端。
     * 
     * @param backlog 可监听的最大连接数，默认1024。用户太设置，但若太大，最终还是取决于内核
     * @return true 成功\
     * @return false 失败
     */
    bool Listen(int backlog = default_backlog)
    {
        int ret = listen(_sockfd, backlog);
        if(ret < 0)
        {
            ERR_LOG("SOCKET %d LISTEN FAILED", _sockfd);
            return false;
        }
        return true;
    }
    /**
     * @brief 客户端向服务端发起连接
     * 
     * @param server_ip 服务端ip
     * @param server_port 服务端port
     * @return true 成功\
     * @return false 失败
     */
    bool Connect(const std::string& server_ip, uint16_t server_port)
    {
        struct sockaddr_in addr;
        bzero(&addr, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(server_ip.c_str());
        addr.sin_port = htons(server_port);

        int ret = connect(_sockfd, (struct sockaddr*)&addr, (socklen_t)sizeof(addr));
        if(ret < 0)
        {
            ERR_LOG("SOCKET %d CONNECT TO [%s:%d] FAILED", _sockfd, server_ip.c_str(), server_port);
            return false;
        }
        return true;
    }

    /**
     * @brief 设置套接字为非阻塞式
     * 
     * @return true 成功\
     * @return false 失败
     */
    bool SetNonBlock()
    {
        int fl = fcntl(_sockfd, F_GETFD);
        if(fl < 0)
        {
            ERR_LOG("SOCKET %d SET NONBLOCK FAILED", _sockfd);
            return false;
        }
        int ret = fcntl(_sockfd, F_SETFD, fl | O_NONBLOCK);
        if(ret < 0)
        {
            ERR_LOG("SOCKET %d SET NONBLOCK FAILED", _sockfd);
            return false;
        }
        return true;
    }
    /**
     * @brief 设置套接字地址重用。
     * 注意：需要在创建套接字后“立即”调用
     * 
     * @return true 成功\
     * @return false 失败
     */
    bool SetReuseAddr()
    {
        int val = 1;
        int ret = setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
        if(ret < 0)
        {
            ERR_LOG("SOCKET %d SET ADDR REUSE FAILED", _sockfd);
            return false;
        }
        val = 1;
        ret = setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
        if(ret < 0)
        {
            ERR_LOG("SOCKET %d SET ADDR REUSE FAILED", _sockfd);
            return false;
        }
        return true;
    }
public:
    /**
     * @brief 服务端获取新连接
     * 
     * @return 成功返回 获取到的通信套接字的文件描述符\
     * @return 失败返回 -1
     */
    int Accept()
    {
        int new_sockfd = accept(_sockfd, nullptr, nullptr);
        if(new_sockfd < 0)
        {
            ERR_LOG("SOCKET %d ACCEPT NEW SOCKET FAILED", _sockfd);
        }
        return new_sockfd;
    }

    /**
     * @brief 为服务端创建监听套接字
     * 
     * @param port 监听套接字绑定的端口
     * @param block 是否设置为非阻塞式IO
     * @return true 成功\
     * @return false 失败
     */
    bool CreateServer(uint16_t port, bool block = false)
    {
        // 1. 创建套接字
        if(CreateSocket() == false) return false;
        // 4. 设置地址重用——地址重用必须在socket之后就立即设置
        if(SetReuseAddr() == false) return false;
        // 2. 设置非阻塞
        if(block)
        {
            if(SetNonBlock() == false) return false;
        }
        // 3. 绑定地址信息
        if(Bind(default_ip, port) == false) return false;

        // 5. 开始监听
        if(Listen() == false) return false;

        return true;
    }
    /**
     * @brief 为客户端创建通信套接字
     * 
     * @param server_ip 服务端ip
     * @param server_port 服务端port
     * @return true 成功\
     * @return false 失败
     */
    bool CreateClient(const std::string& server_ip, uint16_t server_port)
    {
        // 1. 创建套接字
        if(CreateSocket() == false) return false;
        // 2. 向服务端发起通信连接
        if(Connect(server_ip, server_port) == false) return false;

        return true;
    }

    /* 关闭套接字 */
    void Close()
    {
        if(_sockfd > 0)
        {
            close(_sockfd);
            // DBG_LOG("SOCKET %d HAS BEEN CLOSED", _sockfd);
            _sockfd = -1;
        }
    }

    /**
     * @brief 发送数据
     * 
     * @param data 数据起始地址
     * @param size 数据大小，单位Byte
     * @param flags 发送方式，默认为阻塞式发送
     * @return ssize_t 实际发送的数据大小
     * @return -1 发送失败
     */
    ssize_t Send(const void* data, size_t size, int flags = 0)
    {
        ssize_t n = send(_sockfd, data, size, flags);// flags = 0：阻塞式
        if(n < 0)
        {
            if(errno == EAGAIN || errno == EINTR) return 0;

            ERR_LOG("SOCKET %d SEND DATA FAILED", _sockfd);
            return -1;
        }
        return n;
    }
    /**
     * @brief 非阻塞式 发送数据
     * 
     * @param data 数据起始地址
     * @param size 数据大小，单位Byte
     * @return ssize_t 实际发送的数据大小
     * @return -1 发送失败
     */
    ssize_t NonBlockSend(const void* data, size_t size)
    {
        return Send(data, size, MSG_DONTWAIT);
    }
    /**
     * @brief 接收数据
     * 
     * @param out 数据保存到的空间地址
     * @param size 接收数据的大小，单位Byte
     * @param flags 接收方式，默认为阻塞式接收
     * @return ssize_t 实际接收到的数据的大小
     * @return -1 接收失败
     */
    ssize_t Recv(void* out, size_t size, int flags = 0)
    {
        ssize_t n = recv(_sockfd, out, size, flags);// flags = 0：阻塞式
        if(n < 0)
        {
            if(errno == EAGAIN || errno == EINTR) return 0;

            ERR_LOG("SOCKET %d RECV DATA FAILED", _sockfd);
            return -1;
        }
        if(n == 0)
        {
            ERR_LOG("SOCKET %d RECV DATA FAILED BECAUSE IT HAS BEEN CLOSED", _sockfd);
            return 0;
        }
        return n;
    }
    /**
     * @brief 非阻塞式 接收数据
     * 
     * @param out 数据保存到的空间地址
     * @param size 接收数据的大小，单位Byte
     * @return ssize_t 实际接收到的数据的大小
     * @return -1 接收失败
     */
    ssize_t NonBlockRecv(void* out, size_t size)
    {
        return Recv(out, size, MSG_DONTWAIT);
    }

    /**
     * @brief 交换套接字。交换封装的文件描述符
     * 
     * @param other 与当前套接字交换的套接字
     */
    void Swap(Socket& other)
    {
        std::swap(_sockfd, other._sockfd);
    }
};

#endif